/*
 * userapp.c
 *
 * User Application for Alpha-Data ADM-XRC-5T2 accelerator.
 *
 * App  : N-Body Simulation
 * User : Brittle Tsoi
 * Date : 2009 July
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>

#include <admxrc2.h>
#include "userapp.h"
#include "fpga_util.h"

volatile uint32_t* fpgaReg;
volatile uint8_t*  fpgaMem;

typedef struct { // type for particle
  float m, x, y, z, vx, vy, vz;
} p_type;

typedef struct { // type for device
  float m, x, y, z;
} d_type;

typedef struct { // type for acceleration
  float ax, ay, az;
} a_type;

int N=81920;
float EPS=0;
int LOOP=1;

void error(char *msg);

// data size is 0x230000 bytes
int main(int argc, char *argv[])
{
  uint32_t           *buf;
  uint32_t           regval;

  FILE *fp;

  p_type *p;
  d_type *d;
  a_type *a;

  int step;
  int i, j;

  int i_ini;
  int i_end;

  struct timeval tv1, tv2, tv3, tv4;
  long long tv_comp, tv_total;

  volatile float* fpgaRegf;

	if (argc==1) {
		printf("usage: %s <input file> [N] [EPS] [LOOP]\n", argv[0]);
		return 0;
	}

  if (argc>2) N = atoi(argv[2]);
  if (argc>3) sscanf(argv[3], "%f", &EPS);
  if (argc>4) LOOP = atoi(argv[4]);

  printf("nbody : %d particles with EPS=%f in %d iterations\n", N, EPS, LOOP);

  gettimeofday(&tv3, NULL);

  p = (p_type *)malloc(sizeof(p_type)*N);
  if (!p) error("malloc (p)");
  d = (d_type *)malloc(sizeof(d_type)*N);
  if (!d) error("malloc (d)");
  a = (a_type *)malloc(sizeof(a_type)*N);
  if (!a) error("malloc (a)");

  fp = fopen(argv[1], "r");
  if (fread(p, sizeof(float)*7, N, fp) < N) error("fread");
  fclose(fp);

  for (i=0; i<N; i++) {
    d[i].m = p[i].m;
    d[i].x = p[i].x;
    d[i].y = p[i].y;
    d[i].z = p[i].z;
  }

  if (fpga_init()) {
    fpga_cleanup();
    return -1;
  }

  // initial values
  i_ini = 0;
  i_end = N-1;
  fpgaReg[USER_REG + 4] = N-1;   // number of particles
  fpgaReg[USER_REG + 5] = i_ini; // starting i
  fpgaReg[USER_REG + 6] = i_end; // ending i
  fpgaReg[USER_REG + 7] = 0;     // EPS

  fpgaRegf = (float *)fpgaReg;

  gettimeofday(&tv1, NULL);

  // ----------
  for (step=0; step<LOOP; step++) { // in each iteration
    memset(a, 0, sizeof(float)*N*3);

    // send data to FPGA
    i = N * sizeof(d_type);
    regval = 0;
    fpga_setbank(0);
    buf = (uint32_t *)d;
    while (i > 0) {
      fpga_setpage(regval);
      if (fpga_dmawrite(buf, i>0x200000?0x200000:i)) {
        fpga_cleanup();
        return -1;
      }
      i -= 0x200000;
      regval++;
      buf += 0x80000;
    }

    // kick start FPGA
    fpgaReg[USER_REG] = 1;

    i = i_ini;
    while (i<=i_end) {
      // check FIFO count
      regval = fpgaReg[USER_REG];
#ifdef __DEBUG__
printf("%d %08X\n", regval, fpgaReg[USER_REG+31]); fflush(stdout);
#endif

      while ( (i < regval) && (i <= i_end) ) { // new results ready
        for (j=0; j<10 && i<=i_end; j++) {
          a[i].ax += fpgaRegf[USER_REG+1+j*3];
          a[i].ay += fpgaRegf[USER_REG+2+j*3];
          a[i].az += fpgaRegf[USER_REG+3+j*3];
#ifdef __DEBUG__
          printf("%3d/%3d [%08X %08X %08X]\n", i, regval,
              fpgaReg[USER_REG+1+j*3],
              fpgaReg[USER_REG+2+j*3],
              fpgaReg[USER_REG+3+j*3]);
#endif

          i++;
        }
        // signal FIFO for next data
        fpgaReg[USER_REG] = 0x2;
      } // end while i < regval
      sleep_us(3);
    } // end while i < i_end

    for (i=0; i<N; i++) { // for every particle
      // update position
      d[i].x += p[i].vx;
      d[i].y += p[i].vy;
      d[i].z += p[i].vz;

      // update velocity
      p[i].vx += a[i].ax;
      p[i].vy += a[i].ay;
      p[i].vz += a[i].az;
    }

  } // for step
  // ----------
  gettimeofday(&tv2, NULL);

  for (i=0; i<N; i++) {
    p[i].x = d[i].x;
    p[i].y = d[i].y;
    p[i].z = d[i].z;
  }

  fp = fopen("result_nbody.dat", "w");
  if (fwrite(p, sizeof(float)*N*7, 1, fp) < 1)
    error("fwrite");
  fclose(fp);

  free(p);
  free(d);
  free(a);

  fpga_finalize();
  fpga_cleanup();
	
  gettimeofday(&tv4, NULL);

  tv_comp = (tv2.tv_sec - tv1.tv_sec) * 1000000 + (tv2.tv_usec - tv1.tv_usec);
  tv_total = (tv4.tv_sec - tv3.tv_sec) * 1000000 + (tv4.tv_usec - tv3.tv_usec);
  printf("compu time = %lld usec\n", tv_comp);
  printf("total time = %lld usec\n", tv_total);

  return 0;
}

void error(char *msg) {
  printf("Err: %s\n", msg);
  exit(0);
}
